{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "collapsed": true,
    "id": "wY6YVLaoCz82"
   },
   "source": [
    "# 3.6 softmax回归的从零开始实现\n",
    "这一节我们来动手实现softmax回归。首先导入本节实现所需的包或模块。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 34
    },
    "colab_type": "code",
    "id": "_KTZno1ZCz85",
    "outputId": "0aeef999-826e-4659-9147-b71b795847e9"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.1.0\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "import numpy as np\n",
    "import sys\n",
    "sys.path.append(\"..\") # 为了导入上层目录的d2lzh_tensorflow\n",
    "import d2lzh_tensorflow2 as d2l\n",
    "print(tf.__version__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "YvWVFoCGCz89"
   },
   "source": [
    "## 3.6.1 获取和读取数据\n",
    "我们将使用Fashion-MNIST数据集，并设置批量大小为256。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "xs-z-V3NCz89"
   },
   "outputs": [],
   "source": [
    "from tensorflow.keras.datasets import fashion_mnist\n",
    "\n",
    "batch_size=256\n",
    "(x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()\n",
    "x_train = tf.cast(x_train, tf.float32) / 255 #在进行矩阵相乘时需要float型，故强制类型转换为float型\n",
    "x_test = tf.cast(x_test,tf.float32) / 255 #在进行矩阵相乘时需要float型，故强制类型转换为float型\n",
    "train_iter = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(batch_size)\n",
    "test_iter = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(batch_size)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "AM-uxevSCz9A"
   },
   "source": [
    "## 3.6.2 初始化模型参数\n",
    "跟线性回归中的例子一样，我们将使用向量表示每个样本。已知每个样本输入是高和宽均为28像素的图像。模型的输入向量的长度是 28×28=784：该向量的每个元素对应图像中每个像素。由于图像有10个类别，单层神经网络输出层的输出个数为10，因此softmax回归的权重和偏差参数分别为784×10和1×10的矩阵。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "MZdS_bxZCz9B"
   },
   "outputs": [],
   "source": [
    "num_inputs = 784\n",
    "num_outputs = 10\n",
    "W = tf.Variable(tf.random.normal(shape=(num_inputs, num_outputs), mean=0, stddev=0.01, dtype=tf.float32))\n",
    "b = tf.Variable(tf.zeros(num_outputs, dtype=tf.float32))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "wb-yKAMzCz9D"
   },
   "source": [
    "## 3.6.3. 实现softmax运算¶\n",
    "在介绍如何定义softmax回归之前，我们先描述一下对如何对多维Tensor按维度操作。在下面的例子中，给定一个Tensor矩阵X。我们可以只对其中同一列（axis=0）或同一行（axis=1）的元素求和，并在结果中保留行和列这两个维度（keepdims=True）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 105
    },
    "colab_type": "code",
    "id": "xJpGi13bCz9D",
    "outputId": "4f8fd6f5-0652-4f85-85d0-aab0947d5877"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(<tf.Tensor: shape=(1, 3), dtype=int32, numpy=array([[5, 7, 9]], dtype=int32)>,\n",
       " <tf.Tensor: shape=(2, 1), dtype=int32, numpy=\n",
       " array([[ 6],\n",
       "        [15]], dtype=int32)>)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X = tf.constant([[1, 2, 3], [4, 5, 6]])\n",
    "tf.reduce_sum(X, axis=0, keepdims=True), tf.reduce_sum(X, axis=1, keepdims=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "6bF8yM6xCz9F"
   },
   "source": [
    "下面我们就可以定义前面小节里介绍的softmax运算了。在下面的函数中，矩阵X的行数是样本数，列数是输出个数。为了表达样本预测各个输出的概率，softmax运算会先通过exp函数对每个元素做指数运算，再对exp矩阵同行元素求和，最后令矩阵每行各元素与该行元素之和相除。这样一来，最终得到的矩阵每行元素和为1且非负。因此，该矩阵每行都是合法的概率分布。softmax运算的输出矩阵中的任意一行元素代表了一个样本在各个输出类别上的预测概率"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "LQrwwlYGCz9G"
   },
   "outputs": [],
   "source": [
    "def softmax(logits, axis=-1):\n",
    "    return tf.exp(logits)/tf.reduce_sum(tf.exp(logits), axis, keepdims=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "AyZ-egfBCz9I"
   },
   "source": [
    "可以看到，对于随机输入，我们将每个元素变成了非负数，且每一行和为1。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 122
    },
    "colab_type": "code",
    "id": "6MF4TDkXCz9J",
    "outputId": "b3ae12dc-3871-4dcd-a9fc-8e5d6a1ed7dd"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(<tf.Tensor: shape=(2, 5), dtype=float32, numpy=\n",
       " array([[0.3642137 , 0.28537816, 0.0340168 , 0.22774045, 0.08865087],\n",
       "        [0.32305422, 0.16348109, 0.14628816, 0.26491117, 0.10226537]],\n",
       "       dtype=float32)>,\n",
       " <tf.Tensor: shape=(2,), dtype=float32, numpy=array([1., 1.], dtype=float32)>)"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X = tf.random.normal(shape=(2, 5))\n",
    "X_prob = softmax(X)\n",
    "X_prob, tf.reduce_sum(X_prob, axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "8O2hfgKsCz9L"
   },
   "source": [
    "## 3.6.4. 定义模型¶\n",
    "有了softmax运算，我们可以定义上节描述的softmax回归模型了。这里通过reshape函数将每张原始图像改成长度为num_inputs的向量。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "o1HaLQ3VCz9L"
   },
   "outputs": [],
   "source": [
    "def net(X):\n",
    "    logits = tf.matmul(tf.reshape(X, shape=(-1, W.shape[0])), W) + b\n",
    "    return softmax(logits)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "mlt3LTadCz9N"
   },
   "source": [
    "3.6.5. 定义损失函数¶\n",
    "上一节中，我们介绍了softmax回归使用的交叉熵损失函数。为了得到标签的预测概率，我们可以使用pick函数。在下面的例子中，变量y_hat是2个样本在3个类别的预测概率，变量y是这2个样本的标签类别。通过使用pick函数，我们得到了2个样本的标签的预测概率。与“softmax回归”一节数学表述中标签类别离散值从1开始逐一递增不同，在代码中，标签类别的离散值是从0开始逐一递增的。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "nrpGe702Cz9O"
   },
   "source": [
    "下面实现了“softmax回归”一节中介绍的交叉熵损失函数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 34
    },
    "colab_type": "code",
    "id": "2AwDkGcJCz9O",
    "outputId": "1a0460bc-ffe4-4682-ec97-9a6548ff8f34"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: shape=(2,), dtype=float64, numpy=array([0.1, 0.5])>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y_hat = np.array([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])\n",
    "y = np.array([0, 2], dtype='int32')\n",
    "tf.boolean_mask(y_hat, tf.one_hot(y, depth=3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "zMHnt4tdCz9S"
   },
   "outputs": [],
   "source": [
    "def cross_entropy(y_hat, y):\n",
    "        y = tf.cast(tf.reshape(y, shape=[-1, 1]),dtype=tf.int32)\n",
    "        y = tf.one_hot(y, depth=y_hat.shape[-1])\n",
    "        y = tf.cast(tf.reshape(y, shape=[-1, y_hat.shape[-1]]),dtype=tf.int32)\n",
    "        return -tf.math.log(tf.boolean_mask(y_hat, y)+1e-8)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "-fNq8kI_Cz9a"
   },
   "source": [
    "## 3.6.6 计算分类准确率\n",
    "给定一个类别的预测概率分布y_hat，我们把预测概率最大的类别作为输出类别。如果它与真实类别y一致，说明这次预测是正确的。分类准确率即正确预测数量与总预测数量之比。\n",
    "\n",
    "为了演示准确率的计算，下面定义准确率accuracy函数。其中tf.argmax(dim=1)返回矩阵y_hat每行中最大元素的索引，且返回结果与变量y形状相同。相等条件判断式(y_hat.argmax(dim=1) == y)是一个类型为ByteTensor的Tensor，我们用float()将其转换为值为0（相等为假）或1（相等为真）的浮点型Tensor。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "rd4Bx78jCz9a"
   },
   "outputs": [],
   "source": [
    "def accuracy(y_hat, y):\n",
    "    return np.mean((tf.argmax(y_hat, axis=1) == y))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "WgQc6LdqCz9c"
   },
   "source": [
    "让我们继续使用在演示pick函数时定义的变量y_hat和y，并将它们分别作为预测概率分布和标签。可以看到，第一个样本预测类别为2（该行最大元素0.6在本行的索引为2），与真实标签0不一致；第二个样本预测类别为2（该行最大元素0.5在本行的索引为2），与真实标签2一致。因此，这两个样本上的分类准确率为0.5。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 34
    },
    "colab_type": "code",
    "id": "PxCfS-0NCz9d",
    "outputId": "6d94d406-532a-4662-87e9-4c88a71d1d80"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.5"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "accuracy(y_hat, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "v7mQ-Zi6Cz9e"
   },
   "source": [
    "类似地，我们可以评价模型net在数据集data_iter上的准确率"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "QYp3cxWhCz9f"
   },
   "outputs": [],
   "source": [
    "# 描述,对于tensorflow2中，比较的双方必须类型都是int型，所以要将输出和标签都转为int型\n",
    "def evaluate_accuracy(data_iter, net):\n",
    "    acc_sum, n = 0.0, 0\n",
    "    for _, (X, y) in enumerate(data_iter):\n",
    "        y = tf.cast(y,dtype=tf.int64)\n",
    "        acc_sum += np.sum(tf.cast(tf.argmax(net(X), axis=1), dtype=tf.int64) == y)\n",
    "        n += y.shape[0]\n",
    "    return acc_sum / n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 34
    },
    "colab_type": "code",
    "id": "GTSuqfbPCz9h",
    "outputId": "921d04d1-5b7f-4682-bfe9-ec8144e80f93"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.1415\n"
     ]
    }
   ],
   "source": [
    "print(evaluate_accuracy(test_iter, net))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "collapsed": true,
    "id": "yo2WH-rpCz9k"
   },
   "source": [
    "## 3.6.7. 训练模型¶\n",
    "训练softmax回归的实现跟“线性回归的从零开始实现”一节介绍的线性回归中的实现非常相似。我们同样使用小批量随机梯度下降来优化模型的损失函数。在训练模型时，迭代周期数num_epochs和学习率lr都是可以调的超参数。改变它们的值可能会得到分类更准确的模型。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 102
    },
    "colab_type": "code",
    "id": "z3hq8k98Cz9l",
    "outputId": "a60e5d47-4e75-4337-abb6-9f63bb390773"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 1, loss 0.7851, train acc 0.750, test acc 0.794\n",
      "epoch 2, loss 0.5704, train acc 0.812, test acc 0.812\n",
      "epoch 3, loss 0.5254, train acc 0.825, test acc 0.819\n",
      "epoch 4, loss 0.5013, train acc 0.832, test acc 0.825\n",
      "epoch 5, loss 0.4855, train acc 0.836, test acc 0.828\n"
     ]
    }
   ],
   "source": [
    "num_epochs, lr = 5, 0.1\n",
    "# 本函数已保存在d2lzh包中方便以后使用\n",
    "def train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, params=None, lr=None, trainer=None):\n",
    "    for epoch in range(num_epochs):\n",
    "        train_l_sum, train_acc_sum, n = 0.0, 0.0, 0\n",
    "        for X, y in train_iter:\n",
    "            with tf.GradientTape() as tape:\n",
    "                y_hat = net(X)\n",
    "                l = tf.reduce_sum(loss(y_hat, y))\n",
    "            grads = tape.gradient(l, params)\n",
    "            if trainer is None:\n",
    "                # 如果没有传入优化器，则使用原先编写的小批量随机梯度下降\n",
    "                d2l.sgd(params, lr, batch_size, grads)\n",
    "            else:\n",
    "                # tf.keras.optimizers.SGD 直接使用是随机梯度下降 theta(t+1) = theta(t) - learning_rate * gradient\n",
    "                # 这里使用批量梯度下降，需要对梯度除以 batch_size, 对应原书代码的 trainer.step(batch_size)\n",
    "                trainer.apply_gradients(zip([grad / batch_size for grad in grads], params))  # “softmax回归的简洁实现”一节将用到\n",
    "                \n",
    "            y = tf.cast(y, dtype=tf.float32)\n",
    "            train_l_sum += l.numpy()\n",
    "            train_acc_sum += tf.reduce_sum(tf.cast(tf.argmax(y_hat, axis=1) == tf.cast(y, dtype=tf.int64), dtype=tf.int64)).numpy()\n",
    "            n += y.shape[0]\n",
    "        test_acc = evaluate_accuracy(test_iter, net)\n",
    "        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'% (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))\n",
    "\n",
    "trainer = tf.keras.optimizers.SGD(lr)\n",
    "train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, batch_size, [W, b], lr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "A5Yd8TLYHwpK"
   },
   "source": [
    "## 3.6.8 预测"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 129
    },
    "colab_type": "code",
    "id": "hJ9GGDHAHwO3",
    "outputId": "9d272afe-f657-481d-c06e-7f6e851afa65"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 864x864 with 9 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "X, y = iter(test_iter).next()\n",
    "\n",
    "def get_fashion_mnist_labels(labels):\n",
    "    text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat', 'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']\n",
    "    return [text_labels[int(i)] for i in labels]\n",
    "\n",
    "def show_fashion_mnist(images, labels):\n",
    "    # 这⾥的_表示我们忽略（不使⽤）的变量\n",
    "    _, figs = plt.subplots(1, len(images), figsize=(12, 12)) # 这里注意subplot 和subplots 的区别\n",
    "    for f, img, lbl in zip(figs, images, labels):\n",
    "        f.imshow(tf.reshape(img, shape=(28, 28)).numpy())\n",
    "        f.set_title(lbl)\n",
    "        f.axes.get_xaxis().set_visible(False)\n",
    "        f.axes.get_yaxis().set_visible(False)\n",
    "    plt.show()\n",
    "\n",
    "true_labels = get_fashion_mnist_labels(y.numpy())\n",
    "pred_labels = get_fashion_mnist_labels(tf.argmax(net(X), axis=1).numpy())\n",
    "titles = [true + '\\n' + pred for true, pred in zip(true_labels, pred_labels)]\n",
    "\n",
    "show_fashion_mnist(X[0:9], titles[0:9])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "name": "3.6_softmax-regression-scratch.ipynb",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  },
  "pycharm": {
   "stem_cell": {
    "cell_type": "raw",
    "metadata": {
     "collapsed": false
    },
    "source": []
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
